package fun.stgoder.dev.listener.udp;

import fun.stgoder.dev.listener.common.Constants;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;

import java.net.InetSocketAddress;

public class UdpServer {
    public static UdpServer instance;

    public static void createInstance() throws InterruptedException {
        instance = new UdpServer(Constants.SELF_UDP_PORT).start();
    }

    private Bootstrap bootstrap;
    private EventLoopGroup eventLoopGroup;

    private int port;

    private boolean useEpoll;
    private int availableProcessors;
    private Channel[] channels;
    private volatile int i = 0;

    public UdpServer(int port) {
        if (Epoll.isAvailable()) {
            this.useEpoll = true;
        } else {
            this.useEpoll = false;
        }

        this.port = port;

        bootstrap = new Bootstrap();
        eventLoopGroup = useEpoll ? new EpollEventLoopGroup() : new NioEventLoopGroup();

        availableProcessors = Runtime.getRuntime().availableProcessors();
        channels = useEpoll ? new Channel[availableProcessors] : new Channel[1];
    }

    public UdpServer start() throws InterruptedException {
        bootstrap.group(eventLoopGroup).channel(
                useEpoll ? EpollDatagramChannel.class : NioDatagramChannel.class
        ).handler(
                useEpoll ? new ChannelInitializer<EpollDatagramChannel>() {
                    @Override
                    protected void initChannel(EpollDatagramChannel ch) throws Exception {
                        ch.pipeline().addLast(new UdpHandler());
                    }
                } : new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel ch) throws Exception {
                        ch.pipeline().addLast(new UdpHandler());
                    }
                }
        ).option(ChannelOption.SO_BROADCAST, false);

        if (useEpoll) {
            bootstrap.option(EpollChannelOption.SO_REUSEPORT, true);

            for (int i = 0; i < availableProcessors; i++) {
                Channel channel = bootstrap.bind(port).sync().channel();
                channels[i] = channel;
            }
        } else {
            Channel channel = bootstrap.bind(port).sync().channel();
            channels[0] = channel;
        }

        return this;
    }

    private void i() {
        if (i < channels.length - 1) {
            i++;
        } else {
            i = 0;
        }
    }

    public void send(byte[] data, InetSocketAddress to) {
        DatagramPacket dp = new DatagramPacket(Unpooled.copiedBuffer(data), to);
        i();
        channels[i].writeAndFlush(dp);
    }
}
